<!doctype html>
<html lang="zh"><head>
<title>vue-diff算法 - 糖羽仙</title>
<meta charset="UTF-8">
<meta name="keywords" content="blog ACG JavaScript VUE IT">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">

<link rel="shortcut icon" href="/images/head/head.jpg" type="image/jpeg" />
<meta name="description" content="diff算法的本质是找出两个对象之间的差异，目的是尽可能复用节点。">
<meta property="og:type" content="article">
<meta property="og:title" content="vue-diff算法">
<meta property="og:url" content="https://www.tangyuxian.com/2021/07/14/%E5%89%8D%E7%AB%AF/vue/vue-diff%E7%AE%97%E6%B3%95/index.html">
<meta property="og:site_name" content="糖羽仙">
<meta property="og:description" content="diff算法的本质是找出两个对象之间的差异，目的是尽可能复用节点。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140209.png">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140224.png">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140235.png">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140246.png">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140257.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140303.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140311.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140320.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140325.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140331.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140336.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140345.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140351.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140358.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140404.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140410.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140418.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140428.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140436.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140441.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140449.jpeg">
<meta property="og:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140457.jpeg">
<meta property="article:published_time" content="2021-07-14T07:30:02.000Z">
<meta property="article:modified_time" content="2022-11-29T05:34:22.778Z">
<meta property="article:author" content="tangyuxian">
<meta property="article:tag" content="vue">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.tangyuxian.com/PicGoImages/20210812140209.png">

<link rel="stylesheet" href="/lib/fancybox/fancybox.css">
<link rel="stylesheet" href="/lib/mdui_043tiny/mdui.css">


<link rel="stylesheet" href="/lib/iconfont/iconfont.css?v=1763992124127">

    <link rel="stylesheet" href="//at.alicdn.com/t/font_2421060_8z08qcz5sq3.css">

<link rel="stylesheet" href="/css/style.css?v=1763992124127">




    
        <link rel="stylesheet" href="/custom.css?v=1763992124127">
    
        <link rel="stylesheet" href="/themes/umaru/index.css?v=1763992124127">
    
        <link rel="stylesheet" href="/themes/asuka/index.css?v=1763992124127">
    



<script src="/lib/mdui_043tiny/mdui.js" async></script>
<script src="/lib/fancybox/fancybox.umd.js" async></script>
<script src="/lib/lax.min.js" async></script>


<script async src="/js/app.js?v=1763992124127"></script>

 

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1Z7TFL96ZH"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-1Z7TFL96ZH');
</script>


<link rel="stylesheet"  href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/atom-one-dark.min.css">
<!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-shiki-plugin@latest/lib/codeblock.css">
<style>:root{--hl-color:#e1e4e8;--hl-bg:#24292e;--hltools-bg:#1f2428;--hltools-color:#c5c5c5;--hlnumber-bg:#24292e;--hlnumber-color:#444d56;--hlscrollbar-bg:#32383e;--hlexpand-bg:linear-gradient(180deg,rgba(36,41,46,0.1),rgba(36,41,46,0.9))}</style><!-- hexo injector head_end end --><meta name="generator" content="Hexo 7.3.0"></head><body class="nexmoe mdui-drawer-body-left"><div id="nexmoe-background"><div class="nexmoe-bg" style="background-image: url(/images/background/xiaomai.jpg)"></div><div class="nexmoe-character"></div><div class="mdui-appbar mdui-shadow-0"><div class="mdui-toolbar"><a class="mdui-btn mdui-btn-icon mdui-ripple" mdui-drawer="{target: &#039;#drawer&#039;, swipe: true}" title="menu"><i class="mdui-icon nexmoefont icon-menu"></i></a><div class="mdui-toolbar-spacer"></div><a class="mdui-btn mdui-btn-icon" href="/" title="tangyuxian"><img src="/images/head/head.jpg" alt="tangyuxian"></a></div></div></div><div id="nexmoe-header"><div class="nexmoe-drawer mdui-drawer" id="drawer">
    <div class="nexmoe-avatar mdui-ripple">
        <a href="/" title="tangyuxian">
            <img src="/images/head/head.jpg" alt="tangyuxian" alt="tangyuxian">
        </a>
    </div>
    <div class="nexmoe-count">
        <div class="nexmoe-count-item"><span>文章</span>84 <div class="item-radius"></div><div class="item-radius item-right"></div> </div>
        <div class="nexmoe-count-item"><span>标签</span>38<div class="item-radius"></div><div class="item-radius item-right"></div></div>
        <div class="nexmoe-count-item"><span>分类</span>5<div class="item-radius"></div><div class="item-radius item-right"></div></div>
    </div>
    <div class="nexmoe-list mdui-list" mdui-collapse="{accordion: true}">
        
        <a class="nexmoe-list-item mdui-list-item mdui-ripple false" href="/" title="回到首页">
            <i class="mdui-list-item-icon nexmoefont icon-meishi"></i>
            <div class="mdui-list-item-content">
                回到首页
            </div>
        </a>
        
        <a class="nexmoe-list-item mdui-list-item mdui-ripple false" href="/archives.html" title="文章归档">
            <i class="mdui-list-item-icon nexmoefont icon-hanbao1"></i>
            <div class="mdui-list-item-content">
                文章归档
            </div>
        </a>
        
        <a class="nexmoe-list-item mdui-list-item mdui-ripple false" href="/about.html" title="关于糖糖">
            <i class="mdui-list-item-icon nexmoefont icon-jiubei1"></i>
            <div class="mdui-list-item-content">
                关于糖糖
            </div>
        </a>
        
        <a class="nexmoe-list-item mdui-list-item mdui-ripple false" href="/friend.html" title="我的朋友">
            <i class="mdui-list-item-icon nexmoefont icon-cola"></i>
            <div class="mdui-list-item-content">
                我的朋友
            </div>
        </a>
        
    </div>
    
    
        
        <div class="nexmoe-widget-wrap">
    <div class="nexmoe-widget nexmoe-search">
         
            <form id="search_form" action_e="https://cn.bing.com/search?q=site:tangyuxian.com" onsubmit="return search();">
                <label><input id="search_value" name="q" type="search" placeholder="搜索"></label>
            </form>
         
    </div>
</div>




    
        
        <div class="nexmoe-widget-wrap">
	<div class="nexmoe-widget nexmoe-social">
		<a
			class="mdui-ripple"
			href="http://wpa.qq.com/msgrd?v=3&uin=2120252100&site=qq&menu=yes"
			target="_blank"
			mdui-tooltip="{content: 'QQ'}"
			style="
				color: rgb(64, 196, 255);
				background-color: rgba(64, 196, 255, .1);
			"
		>
			<i
				class="nexmoefont icon-QQ"
			></i> </a
		><a
			class="mdui-ripple"
			href="mailto:tangyuxian@vip.qq.com"
			target="_blank"
			mdui-tooltip="{content: 'mail'}"
			style="
				color: rgb(249,8,8);
				background-color: rgba(249,8,8,.1);
			"
		>
			<i
				class="nexmoefont icon-mail-fill"
			></i> </a
		><a
			class="mdui-ripple"
			href="https://blog.csdn.net/qq_40621378?type=blog"
			target="_blank"
			mdui-tooltip="{content: 'CSDN'}"
			style="
				color: rgb(199,29,35);
				background-color: rgba(199,29,35,.1);
			"
		>
			<i
				class="nexmoefont icon-csdn"
			></i> </a
		><a
			class="mdui-ripple"
			href="https://www.cnblogs.com/lovetangyuxian/"
			target="_blank"
			mdui-tooltip="{content: '博客园'}"
			style="
				color: rgb(66, 214, 29);
				background-color: rgba(66, 214, 29, .1);
			"
		>
			<i
				class="nexmoefont icon-bokeyuan"
			></i> </a
		><a
			class="mdui-ripple"
			href="https://github.com/tangyuxian/"
			target="_blank"
			mdui-tooltip="{content: 'GitHub'}"
			style="
				color: rgb(25, 23, 23);
				background-color: rgba(25, 23, 23, .15);
			"
		>
			<i
				class="nexmoefont icon-github"
			></i> </a
		><a
			class="mdui-ripple"
			href="https://gitee.com/tangyuxian"
			target="_blank"
			mdui-tooltip="{content: 'gitee'}"
			style="
				color: rgb(255, 255, 255);
				background-color: rgb(199,29,35);
			"
		>
			<i
				class="nexmoefont icon-mayun"
			></i> </a
		>
	</div>
</div>

    
        
        
  <div class="nexmoe-widget-wrap">
    <h3 class="nexmoe-widget-title">文章分类</h3>
    <div class="nexmoe-widget">

      <ul class="category-list">

        


        

        

        <li class="category-list-item">
          <a class="category-list-link" href="/categories/其它/">其它</a>
          <span class="category-list-count">2</span>
        </li>

        

        <li class="category-list-item">
          <a class="category-list-link" href="/categories/前端/">前端</a>
          <span class="category-list-count">62</span>
        </li>

        

        <li class="category-list-item">
          <a class="category-list-link" href="/categories/后端/">后端</a>
          <span class="category-list-count">8</span>
        </li>

        

        <li class="category-list-item">
          <a class="category-list-link" href="/categories/开发工具/">开发工具</a>
          <span class="category-list-count">11</span>
        </li>

        

        <li class="category-list-item">
          <a class="category-list-link" href="/categories/服务器端/">服务器端</a>
          <span class="category-list-count">1</span>
        </li>

        
      </ul>

    </div>
  </div>


    
        
        
  <div class="nexmoe-widget-wrap">
    <div id="randomtagcloud" class="nexmoe-widget tagcloud nexmoe-rainbow">
      <a href="/tags/JavaScript/" style="font-size: 15.56px;">JavaScript</a> <a href="/tags/Jetbrains/" style="font-size: 10px;">Jetbrains</a> <a href="/tags/TypeScript/" style="font-size: 10px;">TypeScript</a> <a href="/tags/UI%E5%BA%93/" style="font-size: 13.33px;">UI库</a> <a href="/tags/centos/" style="font-size: 10px;">centos</a> <a href="/tags/css/" style="font-size: 11.11px;">css</a> <a href="/tags/docker/" style="font-size: 10px;">docker</a> <a href="/tags/echarts/" style="font-size: 10px;">echarts</a> <a href="/tags/elementPlus/" style="font-size: 13.33px;">elementPlus</a> <a href="/tags/es6/" style="font-size: 14.44px;">es6</a> <a href="/tags/git/" style="font-size: 13.33px;">git</a> <a href="/tags/github/" style="font-size: 10px;">github</a> <a href="/tags/html/" style="font-size: 10px;">html</a> <a href="/tags/http/" style="font-size: 13.33px;">http</a> <a href="/tags/java/" style="font-size: 11.11px;">java</a> <a href="/tags/markerdown/" style="font-size: 11.11px;">markerdown</a> <a href="/tags/nginx/" style="font-size: 10px;">nginx</a> <a href="/tags/node/" style="font-size: 13.33px;">node</a> <a href="/tags/vite/" style="font-size: 10px;">vite</a> <a href="/tags/vue/" style="font-size: 18.89px;">vue</a> <a href="/tags/webpack/" style="font-size: 10px;">webpack</a> <a href="/tags/xuex/" style="font-size: 10px;">xuex</a> <a href="/tags/%E4%BA%8B%E4%BB%B6%E6%B5%81/" style="font-size: 10px;">事件流</a> <a href="/tags/%E4%BA%92%E8%81%94%E7%BD%91/" style="font-size: 10px;">互联网</a> <a href="/tags/%E5%8A%A0%E5%AF%86/" style="font-size: 12.22px;">加密</a> <a href="/tags/%E5%8E%9F%E5%88%9B/" style="font-size: 20px;">原创</a> <a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size: 13.33px;">工具</a> <a href="/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/" style="font-size: 12.22px;">微信小程序</a> <a href="/tags/%E6%80%BB%E7%BB%93/" style="font-size: 13.33px;">总结</a> <a href="/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/" style="font-size: 10px;">数据结构</a> <a href="/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/" style="font-size: 10px;">服务器</a> <a href="/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/" style="font-size: 17.78px;">浏览器</a> <a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size: 14.44px;">算法</a> <a href="/tags/%E7%BC%96%E8%A7%A3%E7%A0%81/" style="font-size: 10px;">编解码</a> <a href="/tags/%E7%BE%8E%E5%8C%96/" style="font-size: 12.22px;">美化</a> <a href="/tags/%E8%A7%86%E9%A2%91/" style="font-size: 11.11px;">视频</a> <a href="/tags/%E8%AE%B0%E5%BD%95/" style="font-size: 11.11px;">记录</a> <a href="/tags/%E8%BD%AC%E8%BD%BD/" style="font-size: 16.67px;">转载</a>
    </div>
    
      <script>
        var maxTagcloud = parseInt(17);
        var tags_length = parseInt(38);
        var tags_arr = [];
        for(var i = 0; i < tags_length; i++){
          tags_arr.push(i);
        }
        tags_arr.sort(function (l, r) {
          return Math.random() > 0.5 ? -1 : 1;
        });
        tags_arr = tags_arr.slice(0, maxTagcloud < tags_length ? tags_length - maxTagcloud : 0);
        for(var tag_i = 0; tag_i < tags_arr.length; tag_i++){
          document.getElementById("randomtagcloud").children[tags_arr[tag_i]].style.display = 'none';
        }
      </script>
    
  </div>

    
        
        
        
  <div class="nexmoe-widget-wrap">
    <h3 class="nexmoe-widget-title">文章归档</h3>
    <div class="nexmoe-widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2025/">2025</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/">2023</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/">2022</a><span class="archive-list-count">23</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/">2021</a><span class="archive-list-count">54</span></li></ul>
    </div>
  </div>



    
        
        
  <div class="nexmoe-widget-wrap">
    <h3 class="nexmoe-widget-title">最新文章</h3>
    <div class="nexmoe-widget">
      <ul>
        
          <li>
            <a href="/2025/11/12/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/%E7%BE%8E%E5%8C%96/%E7%BE%8E%E5%8C%96-%E7%BD%91%E9%A1%B5%E4%B8%BB%E9%A2%98%E5%88%87%E6%8D%A2%E7%9A%84%E6%B8%90%E5%8F%98%E5%8A%A8%E7%94%BB/">美化-网页主题切换的渐变动画</a>
          </li>
        
          <li>
            <a href="/2025/09/29/%E5%89%8D%E7%AB%AF/css/css-%E8%A1%A8%E6%A0%BCtd%E4%B8%8B%E9%AB%98%E5%BA%A6%E4%B8%A2%E5%A4%B1%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/">css-表格td下高度丢失解决办法</a>
          </li>
        
          <li>
            <a href="/2025/09/23/%E5%89%8D%E7%AB%AF/vue/vue-%E5%B0%81%E8%A3%85%E5%87%BD%E6%95%B0%E5%BC%8F%E8%A7%A6%E5%8F%91%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/">vue-封装函数式触发右键菜单组件</a>
          </li>
        
          <li>
            <a href="/2025/06/04/%E5%89%8D%E7%AB%AF/UI%E5%BA%93/UI%E5%BA%93-ElTooltip%E5%8F%AA%E5%9C%A8%E6%96%87%E5%AD%97%E8%B6%85%E5%87%BA%E8%8C%83%E5%9B%B4%E6%97%B6%E6%98%BE%E7%A4%BA%E7%9A%84%E8%A7%86%E7%BA%BF%E6%96%B9%E5%BC%8F/">UI库-ElTooltip只在文字超出范围时显示的视线方式</a>
          </li>
        
          <li>
            <a href="/2025/05/29/%E5%89%8D%E7%AB%AF/UI%E5%BA%93/UI%E5%BA%93-%E5%B0%81%E8%A3%85PageTable%E6%80%BB%E8%A7%88/">UI库-封装PageTable总览</a>
          </li>
        
      </ul>
    </div>
  </div>

    
        
        <div class="nexmoe-widget-wrap">
    <div class="nexmoe-widget nexmoe-link">
		<ul>
        
            <li>
                <a href="https://tangyuxian.com/" target="_blank" >
                    <img src="https://cdn.tangyuxian.com/PicGoImages/202304272004917.jpg" alt="糖糖第一可爱"></img>
                    <p style="color: var(--color-primary)">糖糖第一可爱</p>
                </a>
            </li>
        
		</ul>
    </div>
</div>
<style>
.nexmoe-widget-wrap .nexmoe-link ul li a {
    text-align : center;
}
.nexmoe-widget-wrap .nexmoe-link ul li a img {
    max-width : 100%;
}
.nexmoe-widget-wrap .nexmoe-link ul li a p {
    margin: 10px 0;
}
</style>

    
   
    <div class="nexmoe-copyright">
        &copy; 2025 tangyuxian
        Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
        & <a href="https://github.com/tangyuxian/hexo-theme-tangyuxian" target="_blank">Tangyuxian</a>
        <br><a target="_blank" href="https://beian.miit.gov.cn/">辽ICP备2021002341号</a>
<div style="font-size: 12px">
   <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
   本站总访问量  <a id="busuanzi_value_site_pv"></a> 次<br />
   本站访客数<a id="busuanzi_value_site_uv"></a>人次
</div>

    </div>
</div><!-- .nexmoe-drawer --></div><div id="nexmoe-content"><div class="nexmoe-primary"><div class="nexmoe-post">
  <article>
    
        <div class="nexmoe-post-cover">
            <img src="/images/post/vue.jpg" alt="vue-diff算法" loading="lazy">
            <h1>vue-diff算法</h1>
        </div>
        
    
    <div class="nexmoe-post-meta">
    <div class="nexmoe-rainbow">
        <a class="nexmoefont icon-calendar-fill">2021年07月14日</a>
        
            <a class="nexmoefont icon-appstore-fill -link" href="/categories/%E5%89%8D%E7%AB%AF/">前端</a>
        
        
    </div>
    
    
    
    
    
</div>

    <p>diff算法的本质是找出两个对象之间的差异，目的是尽可能复用节点。</p>
<span id="more"></span>

<h2 id="引一-virtual-dom"><a href="#引一-virtual-dom" class="headerlink" title="引一 virtual dom"></a>引一 virtual dom</h2><ol>
<li>template</li>
<li>渲染函数</li>
<li>vnode(virtual dom)</li>
<li>patch(diff算法)</li>
<li>view</li>
</ol>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140209.png" alt="3a0a37c86e541c31d2c3ed3884ae6308.png" data-caption="3a0a37c86e541c31d2c3ed3884ae6308.png" loading="lazy"></p>
<ul>
<li>Vue.js通过编译将template 模板转换成渲染函数(render ) ，执行渲染函数就可以得到一个虚拟节点树</li>
<li>VNode 虚拟节点：它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说，vnode可以理解成节点描述对象，它描述了应该怎样去创建真实的DOM节点。</li>
<li>patch(也叫做patching算法)：虚拟DOM最核心的部分，它可以将vnode渲染成真实的DOM，这个过程是对比新旧虚拟节点之间有哪些不同，然后根据对比结果找出需要更新的的节点进行更新。这点我们从单词含义就可以看出， patch本身就有补丁、修补的意思，其实际作用是在现有DOM上进行修改来实现更新视图的目的。Vue的Virtual DOM Patching算法是基于Snabbdom的实现，并在些基础上作了很多的调整和改进。</li>
</ul>
<h3 id="什么是virtual-dom"><a href="#什么是virtual-dom" class="headerlink" title="什么是virtual dom"></a><strong>什么是virtual dom</strong></h3><p>通俗易懂的来说就是用一个简单的对象去代替复杂的dom对象。</p>
<p>如果你去打印一下一个真实的DOM节点，就会发现DOM节点上有很多属性，如果Vue每次都生成一个新的真实DOM节点，对性能是巨大的浪费。</p>
<p>Virtual DOM 实际上就是以JavaScript对象(VNode )为基础形成一棵树，对真实DOM的一层抽象。Vue最终的工作就是通过这棵树批量生成真实的DOM节，可以说两者存在一层映射关系。</p>
<p>简单来说，可以把Virtual DOM 理解为一个简单的JS对象，并且最少包含标签名( tag)、属性(attrs)和子元素对象( children)三个属性。不同的框架对这三个属性的命名会有点差别。</p>
<p>对于虚拟DOM，咱们来看一个简单的实例，就是下图所示的这个，详细的阐述了模板 → 渲染函数 → 虚拟DOM树 → 真实DOM的一个过程</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140224.png" alt="dbc527b02888fdd4be3860945e85fa02.png" data-caption="dbc527b02888fdd4be3860945e85fa02.png" loading="lazy"></p>
<p>其实虚拟DOM在Vue.js主要做了两件事：</p>
<ul>
<li>提供与真实DOM节点所对应的虚拟节点vnode</li>
<li>将虚拟节点vnode和旧虚拟节点oldVnode进行对比(diff算法)，然后更新视图</li>
</ul>
<p>总结：<strong>「vdom是为了减轻性能压力。dom是昂贵的，昂贵的一方面在dom本身的重量，dom节点在js里的描述是个非常复杂属性很多原型很长的超级对象，另一方面是浏览器渲染进程和js进程是独立分离的，操作dom时的通信和浏览器本身需要重绘的时耗都是很高的。所以大家机智的搞了个轻量的vdom去模拟dom，vdom每个节点都只挂载js操作的必要属性，每次组件update时都先操作vdom，通过vdom的比对，得到一个真实dom的需要操作的集合。整个机制是在JavaScript层面计算，在完成之前并不会操作DOM，等数据稳定之后再实现精准的修改。」</strong></p>
<h2 id="引二-分析diff算法"><a href="#引二-分析diff算法" class="headerlink" title="引二 分析diff算法"></a>引二 分析diff算法</h2><p>由上我们知道了，新的虚拟DOM和旧的虚拟DOm是通过diff算法进行比较之后更新的。</p>
<h3 id="Vue2-x-diff算法"><a href="#Vue2-x-diff算法" class="headerlink" title="Vue2.x diff算法"></a><strong>Vue2.x diff算法</strong></h3><h3 id="Vue2-x-diff算法原理"><a href="#Vue2-x-diff算法原理" class="headerlink" title="Vue2.x diff算法原理"></a><strong>Vue2.x diff算法原理</strong></h3><p>传统diff算法通过循环递归对节点进行依次对比效率低下，算法复杂度达到O(N^3)，主要原因在于其追求完全比对和最小修改，而React、Vue则是放弃了完全比对及最小修改，才实现从O(N^3) &#x3D;&gt; O(N)。</p>
<p>优化措施有：</p>
<ul>
<li><strong>「分层diff」</strong>：不考虑跨层级移动节点，让新旧两个VDOM树的比对无需循环递归(复杂度大幅优化，直接下降一个数量级的首要条件)。这个前提也是Web UI 中 DOM 节点跨层级的移动操作特别少，可以忽略不计。</li>
</ul>
<p>在同层节点中，采用了**「双端比较的算法」**过程可以概括为：oldCh和newCh各有两个头尾的变量StartIdx和EndIdx，它们的2个变量相互比较，一共有4种比较方式。如果4种比较都没匹配，如果设置了key，就会用key进行比较，在比较的过程中，变量会往中间靠，一旦StartIdx&gt;EndIdx表明oldCh和newCh至少有一个已经遍历完了，就会结束比较；</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140235.png" alt="13ce1945ff1e74d43467f5c3ef82b16f.png" data-caption="13ce1945ff1e74d43467f5c3ef82b16f.png" loading="lazy"></p>
<p>当发生以下情况则跳过比对，变为插入或删除操作：</p>
<ul>
<li><strong>「组件的Type(Tagname)不一致」</strong>，原因是绝大多数情况拥有相同type的两个组件将会生成相似的树形结构，拥有不同type的两个组件将会生成不同的树形结构，所以type不一致可以放弃继续比对。</li>
<li><strong>「列表组件的Key不一致」</strong>，旧树中无新Key或反之。毕竟key是元素的身份id，能直接对应上是否是同一个节点。</li>
<li>对触发了getter&#x2F;setter 的组件进行diff，精准减少diff范围</li>
</ul>
<h3 id="Vue3-0-diff"><a href="#Vue3-0-diff" class="headerlink" title="Vue3.0 diff"></a><strong>Vue3.0 diff</strong></h3><h4 id="diff痛点"><a href="#diff痛点" class="headerlink" title="diff痛点"></a><strong>diff痛点</strong></h4><p>vue2.x中的虚拟dom是进行**「全量的对比」<strong>，在运行时会对所有节点生成一个虚拟节点树，当页面数据发生变更好，会遍历判断virtual dom所有节点(包括一些不会变化的节点)有没有发生变化；虽然说diff算法确实减少了多DOM节点的直接操作，但是这个</strong>「减少是有成本的」<strong>，如果是复杂的大型项目，必然存在很复杂的父子关系的VNode,</strong>「而Vue2.x的diff算法，会不断地递归调用 patchVNode，不断堆叠而成的几毫秒，最终就会造成 VNode 更新缓慢」**。</p>
<p>那么Vue3.0是如何解决这些问题的呢</p>
<h5 id="动静结合-PatchFlag"><a href="#动静结合-PatchFlag" class="headerlink" title="动静结合 PatchFlag"></a><strong>动静结合 PatchFlag</strong></h5><p>在Vue3.0中，在这个模版编译时，编译器会在动态标签末尾加上 &#x2F;* Text*&#x2F; PatchFlag。**「也就是在生成VNode的时候，同时打上标记，在这个基础上再进行核心的diff算法」**并且 PatchFlag 会标识动态的属性类型有哪些，比如这里 的TEXT 表示只有节点中的文字是动态的。而patchFlag的类型也很多。这里直接引用一张图片。</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140246.png" alt="21d07297b28cf0c636daa79431065f2a.png" data-caption="21d07297b28cf0c636daa79431065f2a.png" loading="lazy"></p>
<p>其中大致可以分为两类：</p>
<ul>
<li>当 patchFlag 的值「大于」 0 时，代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。</li>
<li>当 patchFlag 的值「小于」 0 时，代表所对应的元素在 patchVNode 时，是需要被 full diff，即进行递归遍历 VNode tree 的比较更新过程。</li>
</ul>
<p>看源码：</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">export function render(_ctx, _cache, $props, $setup, $data, $options) {</span></span>
<span class="line"><span style="color: #e1e4e8"> return (_openBlock(), _createBlock(&quot;div&quot;, null, [</span></span>
<span class="line"><span style="color: #e1e4e8">  _createVNode(&quot;p&quot;, null, &quot;&#39;HelloWorld&#39;&quot;),</span></span>
<span class="line"><span style="color: #e1e4e8">  _createVNode(&quot;p&quot;, null, _toDisplayString(_ctx.msg), 1 /* TEXT */)</span></span>
<span class="line"><span style="color: #e1e4e8"> </span></span>
<span class="line"><span style="color: #e1e4e8"> ]))</span></span>
<span class="line"><span style="color: #e1e4e8">}</span></span>
<span class="line"><span style="color: #e1e4e8">****</span></span></code></pre></div></div></figure>

<p>这里的<code>_createVNode(&quot;p&quot;, null, _toDisplayString(_ctx.msg), 1 /* TEXT */)</code>就是对变量节点进行标记。</p>
<p>总结：<strong>「Vue3.0对于不参与更新的元素，做静态标记并提示，只会被创建一次，在渲染时直接复用。」</strong></p>
<p>可参考<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_45613931/article/details/109470718">CSDN文章:Vue3的diff算法优化部分</a></p>
<hr>
<h2 id="一-什么时候用到了diff算法-diff算法作用域？"><a href="#一-什么时候用到了diff算法-diff算法作用域？" class="headerlink" title="一 什么时候用到了diff算法,diff算法作用域？"></a>一 什么时候用到了diff算法,diff算法作用域？</h2><h3 id="1-1diff算法的作用域"><a href="#1-1diff算法的作用域" class="headerlink" title="1.1diff算法的作用域"></a>1.1diff算法的作用域</h3><p><strong>patch概念引入</strong></p>
<p>在vue update过程中在遍历子代vnode的过程中，会用不同的patch方法来patch新老vnode，如果找到对应的 newVnode 和 oldVnode,就可以复用利用里面的真实dom节点。避免了重复创建元素带来的性能开销。毕竟浏览器创造真实的dom，操纵真实的dom，性能代价是昂贵的。</p>
<p>patch过程中，如果面对当前vnode存在有很多chidren的情况,那么需要分别遍历patch新的children Vnode和老的 children vnode。</p>
<p><strong>存在chidren的vnode类型</strong></p>
<p>首先思考一下什么类型的vnode会存在children。</p>
<p><strong>①element元素类型vnode</strong></p>
<p>第一中情况就是element类型vnode 会存在 children vode，此时的三个span标签就是chidren vnode情况</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">&lt;div&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 苹果🍎 &lt;/span&gt; </span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 香蕉🍌 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 鸭梨🍐 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">&lt;/div&gt;</span></span></code></pre></div></div></figure>

<p><strong>在vue3.0源码中 ，patchElement用于处理element类型的vnode</strong></p>
<p><strong>②flagment碎片类型vnode</strong></p>
<p>在Vue3.0中，引入了一个fragment碎片概念。<br>你可能会问，什么是碎片？如果你创建一个Vue组件，那么它只能有一个根节点。</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">&lt;template&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 苹果🍎 &lt;/span&gt; </span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 香蕉🍌 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 鸭梨🍐 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">&lt;/template&gt;</span></span></code></pre></div></div></figure>

<p>这样可能会报出警告，原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。</p>
<p>flagment出现就是用看起来像一个普通的DOM元素，但它是虚拟的，根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中，而不需要创建一个多余的DOM节点。</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8"> &lt;Fragment&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 苹果🍎 &lt;/span&gt; </span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 香蕉🍌 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">   &lt;span&gt; 鸭梨🍐 &lt;/span&gt;</span></span>
<span class="line"><span style="color: #e1e4e8">&lt;/Fragment&gt;</span></span></code></pre></div></div></figure>

<p><strong>在vue3.0源码中 ，processFragment用于处理Fragment类型的vnode</strong></p>
<h3 id="1-2-patchChildren"><a href="#1-2-patchChildren" class="headerlink" title="1.2 patchChildren"></a>1.2 patchChildren</h3><p>从上文中我们得知了存在children的vnode类型，那么存在children就需要patch每一个<br>children vnode依次向下遍历。那么就需要一个patchChildren方法，依次patch子类vnode。</p>
<p><strong>patchChildren</strong></p>
<p>vue3.0中 在patchChildren方法中有这么一段源码</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">if (patchFlag &gt; 0) {</span></span>
<span class="line"><span style="color: #e1e4e8">      if (patchFlag &amp; PatchFlags.KEYED_FRAGMENT) { </span></span>
<span class="line"><span style="color: #e1e4e8">         /* 对于存在key的情况用于diff算法 */</span></span>
<span class="line"><span style="color: #e1e4e8">        patchKeyedChildren(</span></span>
<span class="line"><span style="color: #e1e4e8">          c1 as VNode[],</span></span>
<span class="line"><span style="color: #e1e4e8">          c2 as VNodeArrayChildren,</span></span>
<span class="line"><span style="color: #e1e4e8">          container,</span></span>
<span class="line"><span style="color: #e1e4e8">          anchor,</span></span>
<span class="line"><span style="color: #e1e4e8">          parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">          parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">          isSVG,</span></span>
<span class="line"><span style="color: #e1e4e8">          optimized</span></span>
<span class="line"><span style="color: #e1e4e8">        )</span></span>
<span class="line"><span style="color: #e1e4e8">        return</span></span>
<span class="line"><span style="color: #e1e4e8">      } else if (patchFlag &amp; PatchFlags.UNKEYED_FRAGMENT) {</span></span>
<span class="line"><span style="color: #e1e4e8">         /* 对于不存在key的情况,直接patch  */</span></span>
<span class="line"><span style="color: #e1e4e8">        patchUnkeyedChildren( </span></span>
<span class="line"><span style="color: #e1e4e8">          c1 as VNode[],</span></span>
<span class="line"><span style="color: #e1e4e8">          c2 as VNodeArrayChildren,</span></span>
<span class="line"><span style="color: #e1e4e8">          container,</span></span>
<span class="line"><span style="color: #e1e4e8">          anchor,</span></span>
<span class="line"><span style="color: #e1e4e8">          parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">          parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">          isSVG,</span></span>
<span class="line"><span style="color: #e1e4e8">          optimized</span></span>
<span class="line"><span style="color: #e1e4e8">        )</span></span>
<span class="line"><span style="color: #e1e4e8">        return</span></span>
<span class="line"><span style="color: #e1e4e8">      }</span></span>
<span class="line"><span style="color: #e1e4e8">    }</span></span></code></pre></div></div></figure>

<p><strong>patchChildren根据是否存在key进行真正的diff或者直接patch。</strong></p>
<p><strong>既然diff算法存在patchChildren方法中，而patchChildren方法用在Fragment类型和element类型的vnode中，这样也就解释了diff算法的作用域是什么。</strong></p>
<h3 id="1-3-diff算法作用？"><a href="#1-3-diff算法作用？" class="headerlink" title="1.3 diff算法作用？"></a>1.3 diff算法作用？</h3><p>通过前言我们知道，存在这children的情况的vnode，需要通过patchChildren遍历children依次进行patch操作，如果在patch期间，再发现存在vnode情况，那么会递归的方式依次向下patch，那么找到与新的vnode对应的vnode显的如此重要。</p>
<p>我们用两幅图来向大家展示vnode变化。</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140257.jpeg" alt="WechatIMG13940.jpeg" data-caption="WechatIMG13940.jpeg" loading="lazy"><br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140303.jpeg" alt="WechatIMG13941.jpeg" data-caption="WechatIMG13941.jpeg" loading="lazy"></p>
<p>如上两幅图表示在一次更新中新老dom树变化情况。</p>
<p><strong>假设不存在diff算法，依次按照先后顺序patch会发生什么</strong></p>
<p>如果<strong>不存在diff算法</strong>，而是直接patchchildren 就会出现如下图的逻辑。</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140311.jpeg" alt="WechatIMG13942.jpeg" data-caption="WechatIMG13942.jpeg" loading="lazy"></p>
<p><strong>第一次patchChidren</strong></p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140320.jpeg" alt="WechatIMG13943.jpeg" data-caption="WechatIMG13943.jpeg" loading="lazy"></p>
<p><strong>第二次patchChidren</strong></p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140325.jpeg" alt="WechatIMG13944.jpeg" data-caption="WechatIMG13944.jpeg" loading="lazy"></p>
<p><strong>第三次patchChidren</strong>‘</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140331.jpeg" alt="WechatIMG13945.jpeg" data-caption="WechatIMG13945.jpeg" loading="lazy"></p>
<p><strong>第四次patchChidren</strong></p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140336.jpeg" alt="WechatIMG13947.jpeg" data-caption="WechatIMG13947.jpeg" loading="lazy"><br>如果没有用到diff算法，而是依次patch虚拟dom树，那么如上稍微<strong>修改dom顺序</strong>，就会在patch过程中没有一对正确的新老vnode，所以老vnode的节点没有一个可以复用，这样就需要重新创造新的节点，浪费了性能开销，这显然不是我们需要的。</p>
<p>那么diff算法的作用就来了。</p>
<p><strong>diff作用就是在patch子vnode过程中，找到与新vnode对应的老vnode，复用真实的dom节点，避免不必要的性能开销</strong></p>
<h2 id="二-diff算法具体做了什么-重点-？"><a href="#二-diff算法具体做了什么-重点-？" class="headerlink" title="二 diff算法具体做了什么(重点)？"></a>二 diff算法具体做了什么(重点)？</h2><p>在正式讲diff算法之前，在patchChildren的过程中，存在 <strong>patchKeyedChildren</strong><br><strong>patchUnkeyedChildren</strong></p>
<p>patchKeyedChildren 是正式的开启diff的流程，那么patchUnkeyedChildren的作用是什么呢？ 我们来看看针对没有key的情况patchUnkeyedChildren会做什么。</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">c1 = c1 || EMPTY_ARR</span></span>
<span class="line"><span style="color: #e1e4e8">   c2 = c2 || EMPTY_ARR</span></span>
<span class="line"><span style="color: #e1e4e8">   const oldLength = c1.length</span></span>
<span class="line"><span style="color: #e1e4e8">   const newLength = c2.length</span></span>
<span class="line"><span style="color: #e1e4e8">   const commonLength = Math.min(oldLength, newLength)</span></span>
<span class="line"><span style="color: #e1e4e8">   let i</span></span>
<span class="line"><span style="color: #e1e4e8">   for (i = 0; i &lt; commonLength; i++) { /* 依次遍历新老vnode进行patch */</span></span>
<span class="line"><span style="color: #e1e4e8">     const nextChild = (c2[i] = optimized</span></span>
<span class="line"><span style="color: #e1e4e8">       ? cloneIfMounted(c2[i] as VNode)</span></span>
<span class="line"><span style="color: #e1e4e8">       : normalizeVNode(c2[i]))</span></span>
<span class="line"><span style="color: #e1e4e8">     patch(</span></span>
<span class="line"><span style="color: #e1e4e8">       c1[i],</span></span>
<span class="line"><span style="color: #e1e4e8">       nextChild,</span></span>
<span class="line"><span style="color: #e1e4e8">       container,</span></span>
<span class="line"><span style="color: #e1e4e8">       null,</span></span>
<span class="line"><span style="color: #e1e4e8">       parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">       parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">       isSVG,</span></span>
<span class="line"><span style="color: #e1e4e8">       optimized</span></span>
<span class="line"><span style="color: #e1e4e8">     )</span></span>
<span class="line"><span style="color: #e1e4e8">   }</span></span>
<span class="line"><span style="color: #e1e4e8">   if (oldLength &gt; newLength) { /* 老vnode 数量大于新的vnode，删除多余的节点 */</span></span>
<span class="line"><span style="color: #e1e4e8">     unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)</span></span>
<span class="line"><span style="color: #e1e4e8">   } else { /* /* 老vnode 数量小于于新的vnode，创造新的即诶安 */</span></span>
<span class="line"><span style="color: #e1e4e8">     mountChildren(</span></span>
<span class="line"><span style="color: #e1e4e8">       c2,</span></span>
<span class="line"><span style="color: #e1e4e8">       container,</span></span>
<span class="line"><span style="color: #e1e4e8">       anchor,</span></span>
<span class="line"><span style="color: #e1e4e8">       parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">       parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">       isSVG,</span></span>
<span class="line"><span style="color: #e1e4e8">       optimized,</span></span>
<span class="line"><span style="color: #e1e4e8">       commonLength</span></span>
<span class="line"><span style="color: #e1e4e8">     )</span></span>
<span class="line"><span style="color: #e1e4e8">   }</span></span></code></pre></div></div></figure>

<p>我们可以得到结论，对于不存在key情况<br><strong>① 比较新老children的length获取最小值 然后对于公共部分，进行从新patch工作。</strong><br><strong>② 如果老节点数量大于新的节点数量 ，移除多出来的节点。</strong><br><strong>③ 如果新的节点数量大于老节点的数量，从新 mountChildren新增的节点。</strong></p>
<p>那么对于存在key情况呢？ 会用到diff算法 ， diff算法做了什么呢？</p>
<p><strong>patchKeyedChildren方法究竟做了什么？</strong><br>我们先来看看一些声明的变量。</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">/*  c1 老的vnode c2 新的vnode  */</span></span>
<span class="line"><span style="color: #e1e4e8">let i = 0              /* 记录索引 */</span></span>
<span class="line"><span style="color: #e1e4e8">const l2 = c2.length   /* 新vnode的数量 */</span></span>
<span class="line"><span style="color: #e1e4e8">let e1 = c1.length - 1 /* 老vnode 最后一个节点的索引 */</span></span>
<span class="line"><span style="color: #e1e4e8">let e2 = l2 - 1        /* 新节点最后一个节点的索引 */</span></span></code></pre></div></div></figure>

<h3 id="①第一步从头开始向尾寻找"><a href="#①第一步从头开始向尾寻找" class="headerlink" title="①第一步从头开始向尾寻找"></a>①第一步从头开始向尾寻找</h3><p>(a b) c<br>(a b) d e</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">/* 从头对比找到有相同的节点 patch ，发现不同，立即跳出*/</span></span>
<span class="line"><span style="color: #e1e4e8">   while (i &lt;= e1 &amp;&amp; i &lt;= e2) {</span></span>
<span class="line"><span style="color: #e1e4e8">     const n1 = c1[i]</span></span>
<span class="line"><span style="color: #e1e4e8">     const n2 = (c2[i] = optimized</span></span>
<span class="line"><span style="color: #e1e4e8">       ? cloneIfMounted(c2[i] as VNode)</span></span>
<span class="line"><span style="color: #e1e4e8">       : normalizeVNode(c2[i]))</span></span>
<span class="line"><span style="color: #e1e4e8">       /* 判断key ，type是否相等 */</span></span>
<span class="line"><span style="color: #e1e4e8">     if (isSameVNodeType(n1, n2)) {</span></span>
<span class="line"><span style="color: #e1e4e8">       patch(</span></span>
<span class="line"><span style="color: #e1e4e8">         n1,</span></span>
<span class="line"><span style="color: #e1e4e8">         n2,</span></span>
<span class="line"><span style="color: #e1e4e8">         container, </span></span>
<span class="line"><span style="color: #e1e4e8">         parentAnchor,</span></span>
<span class="line"><span style="color: #e1e4e8">         parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">         parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">         isSVG,</span></span>
<span class="line"><span style="color: #e1e4e8">         optimized</span></span>
<span class="line"><span style="color: #e1e4e8">       )</span></span>
<span class="line"><span style="color: #e1e4e8">     } else {</span></span>
<span class="line"><span style="color: #e1e4e8">       break</span></span>
<span class="line"><span style="color: #e1e4e8">     }</span></span>
<span class="line"><span style="color: #e1e4e8">     i++</span></span>
<span class="line"><span style="color: #e1e4e8">   }</span></span></code></pre></div></div></figure>

<p>第一步的事情就是从头开始寻找相同的vnode，然后进行patch,如果发现不是相同的节点，那么立即跳出循环。</p>
<p>具体流程如图所示</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140345.jpeg" alt="在这里插入图片描述" data-caption="在这里插入图片描述" loading="lazy"></p>
<p><strong>isSameVNodeType</strong></p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">export function isSameVNodeType(n1: VNode, n2: VNode): boolean {</span></span>
<span class="line"><span style="color: #e1e4e8">  return n1.type === n2.type &amp;&amp; n1.key === n2.key</span></span>
<span class="line"><span style="color: #e1e4e8">}</span></span></code></pre></div></div></figure>

<p>isSameVNodeType 作用就是判断当前vnode类型 和 vnode的 key是否相等</p>
<h3 id="②第二步从尾开始同前diff"><a href="#②第二步从尾开始同前diff" class="headerlink" title="②第二步从尾开始同前diff"></a>②第二步从尾开始同前diff</h3><p>a (b c)<br>d e (b c)</p>
<figure class="shiki javascript"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #6A737D">/* 如果第一步没有patch完，立即，从后往前开始patch ,如果发现不同立即跳出循环 */</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">while</span><span style="color: #E1E4E8"> (i </span><span style="color: #F97583">&lt;=</span><span style="color: #E1E4E8"> e1 </span><span style="color: #F97583">&amp;&amp;</span><span style="color: #E1E4E8"> i </span><span style="color: #F97583">&lt;=</span><span style="color: #E1E4E8"> e2) {</span></span>
<span class="line"><span style="color: #E1E4E8">     </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">n1</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> c1[e1]</span></span>
<span class="line"><span style="color: #E1E4E8">     </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">n2</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> (c2[e2] </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> optimized</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">?</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">cloneIfMounted</span><span style="color: #E1E4E8">(c2[e2] </span><span style="color: #F97583">as</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">VNode</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">:</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">normalizeVNode</span><span style="color: #E1E4E8">(c2[e2]))</span></span>
<span class="line"><span style="color: #E1E4E8">     </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (</span><span style="color: #B392F0">isSameVNodeType</span><span style="color: #E1E4E8">(n1, n2)) {</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #B392F0">patch</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">         n1,</span></span>
<span class="line"><span style="color: #E1E4E8">         n2,</span></span>
<span class="line"><span style="color: #E1E4E8">         container,</span></span>
<span class="line"><span style="color: #E1E4E8">         parentAnchor,</span></span>
<span class="line"><span style="color: #E1E4E8">         parentComponent,</span></span>
<span class="line"><span style="color: #E1E4E8">         parentSuspense,</span></span>
<span class="line"><span style="color: #E1E4E8">         isSVG,</span></span>
<span class="line"><span style="color: #E1E4E8">         optimized</span></span>
<span class="line"><span style="color: #E1E4E8">       )</span></span>
<span class="line"><span style="color: #E1E4E8">     } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">break</span></span>
<span class="line"><span style="color: #E1E4E8">     }</span></span>
<span class="line"><span style="color: #E1E4E8">     e1</span><span style="color: #F97583">--</span></span>
<span class="line"><span style="color: #E1E4E8">     e2</span><span style="color: #F97583">--</span></span>
<span class="line"><span style="color: #E1E4E8">   }</span></span></code></pre></div></div></figure>

<p>经历第一步操作之后，如果发现没有patch完，那么立即进行第二部，从尾部开始遍历依次向前diff。</p>
<p>如果发现不是相同的节点，那么立即跳出循环。</p>
<p>具体流程如图所示<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140351.jpeg" alt="WechatIMG13952.jpeg" data-caption="WechatIMG13952.jpeg" loading="lazy"></p>
<p><strong>③④主要针对新增和删除元素的情况，前提是元素没有发生移动， 如果有元素发生移动就要走⑤逻辑。</strong></p>
<h3 id="③-如果老节点是否全部patch，新节点没有被patch完-创建新的vnode"><a href="#③-如果老节点是否全部patch，新节点没有被patch完-创建新的vnode" class="headerlink" title="③ 如果老节点是否全部patch，新节点没有被patch完,创建新的vnode"></a>③ 如果老节点是否全部patch，新节点没有被patch完,创建新的vnode</h3><p>(a b)<br>(a b) c<br>i &#x3D; 2, e1 &#x3D; 1, e2 &#x3D; 2<br>(a b)<br>c (a b)<br>i &#x3D; 0, e1 &#x3D; -1, e2 &#x3D; 0</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">/* 如果新的节点大于老的节点数 ，对于剩下的节点全部以新的vnode处理（ 这种情况说明已经patch完相同的vnode  ） */</span></span>
<span class="line"><span style="color: #e1e4e8">    if (i &gt; e1) {</span></span>
<span class="line"><span style="color: #e1e4e8">      if (i &lt;= e2) {</span></span>
<span class="line"><span style="color: #e1e4e8">        const nextPos = e2 + 1</span></span>
<span class="line"><span style="color: #e1e4e8">        const anchor = nextPos &lt; l2 ? (c2[nextPos] as VNode).el : parentAnchor</span></span>
<span class="line"><span style="color: #e1e4e8">        while (i &lt;= e2) {</span></span>
<span class="line"><span style="color: #e1e4e8">          patch( /* 创建新的节点*/</span></span>
<span class="line"><span style="color: #e1e4e8">            null,</span></span>
<span class="line"><span style="color: #e1e4e8">            (c2[i] = optimized</span></span>
<span class="line"><span style="color: #e1e4e8">              ? cloneIfMounted(c2[i] as VNode)</span></span>
<span class="line"><span style="color: #e1e4e8">              : normalizeVNode(c2[i])),</span></span>
<span class="line"><span style="color: #e1e4e8">            container,</span></span>
<span class="line"><span style="color: #e1e4e8">            anchor,</span></span>
<span class="line"><span style="color: #e1e4e8">            parentComponent,</span></span>
<span class="line"><span style="color: #e1e4e8">            parentSuspense,</span></span>
<span class="line"><span style="color: #e1e4e8">            isSVG</span></span>
<span class="line"><span style="color: #e1e4e8">          )</span></span>
<span class="line"><span style="color: #e1e4e8">          i++</span></span>
<span class="line"><span style="color: #e1e4e8">        }</span></span>
<span class="line"><span style="color: #e1e4e8">      }</span></span>
<span class="line"><span style="color: #e1e4e8">    }</span></span></code></pre></div></div></figure>

<p><strong>i &gt; e1</strong></p>
<p>如果新的节点大于老的节点数 ，对于剩下的节点全部以新的vnode处理（ 这种情况说明已经patch完相同的vnode ），也就是要全部create新的vnode.</p>
<p>具体逻辑如图所示<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140358.jpeg" alt="WechatIMG13955.jpeg" data-caption="WechatIMG13955.jpeg" loading="lazy"></p>
<h3 id="④-如果新节点全部被patch，老节点有剩余，那么卸载所有老节点"><a href="#④-如果新节点全部被patch，老节点有剩余，那么卸载所有老节点" class="headerlink" title="④ 如果新节点全部被patch，老节点有剩余，那么卸载所有老节点"></a>④ 如果新节点全部被patch，老节点有剩余，那么卸载所有老节点</h3><p><strong>i &gt; e2</strong><br>(a b) c<br>(a b)<br>i &#x3D; 2, e1 &#x3D; 2, e2 &#x3D; 1<br>a (b c)<br>(b c)<br>i &#x3D; 0, e1 &#x3D; 0, e2 &#x3D; -1</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">else if (i &gt; e2) {</span></span>
<span class="line"><span style="color: #e1e4e8">   while (i &lt;= e1) {</span></span>
<span class="line"><span style="color: #e1e4e8">      unmount(c1[i], parentComponent, parentSuspense, true)</span></span>
<span class="line"><span style="color: #e1e4e8">      i++</span></span>
<span class="line"><span style="color: #e1e4e8">   }</span></span>
<span class="line"><span style="color: #e1e4e8">}</span></span></code></pre></div></div></figure>

<p>对于老的节点大于新的节点的情况 ，对于超出的节点全部卸载 （ 这种情况说明已经patch完相同的vnode ）</p>
<p>具体逻辑如图所示<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140404.jpeg" alt="WechatIMG13956.jpeg" data-caption="WechatIMG13956.jpeg" loading="lazy"></p>
<h3 id="⑤-不确定的元素-（-这种情况说明没有patch完相同的vnode-），我们可以接着①②的逻辑继续往下看"><a href="#⑤-不确定的元素-（-这种情况说明没有patch完相同的vnode-），我们可以接着①②的逻辑继续往下看" class="headerlink" title="⑤ 不确定的元素 （ 这种情况说明没有patch完相同的vnode ），我们可以接着①②的逻辑继续往下看"></a>⑤ 不确定的元素 （ 这种情况说明没有patch完相同的vnode ），我们可以接着①②的逻辑继续往下看</h3><p><strong>diff核心</strong></p>
<p>在①②情况下没有遍历完的节点如下图所示。<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140410.jpeg" alt="WechatIMG13957.jpeg" data-caption="WechatIMG13957.jpeg" loading="lazy"></p>
<p>剩下的节点。</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140418.jpeg" alt="WechatIMG13953.jpeg" data-caption="WechatIMG13953.jpeg" loading="lazy"></p>
<figure class="shiki javascript"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">s1</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> i  </span><span style="color: #6A737D">//第一步遍历到的index</span></span>
<span class="line"><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">s2</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> i </span></span>
<span class="line"><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">keyToNewIndexMap</span><span style="color: #F97583">:</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Map</span><span style="color: #E1E4E8">&lt;</span><span style="color: #79B8FF">string</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">|</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">number</span><span style="color: #E1E4E8">, </span><span style="color: #79B8FF">number</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">new</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Map</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #6A737D">/* 把没有比较过的新的vnode节点,通过map保存 */</span></span>
<span class="line"><span style="color: #F97583">for</span><span style="color: #E1E4E8"> (i </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> s2; i </span><span style="color: #F97583">&lt;=</span><span style="color: #E1E4E8"> e2; i</span><span style="color: #F97583">++</span><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (nextChild.key </span><span style="color: #F97583">!=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">null</span><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">    keyToNewIndexMap.</span><span style="color: #B392F0">set</span><span style="color: #E1E4E8">(nextChild.key, i)</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"><span style="color: #F97583">let</span><span style="color: #E1E4E8"> j</span></span>
<span class="line"><span style="color: #F97583">let</span><span style="color: #E1E4E8"> patched </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8"> </span></span>
<span class="line"><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">toBePatched</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> e2 </span><span style="color: #F97583">-</span><span style="color: #E1E4E8"> s2 </span><span style="color: #F97583">+</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span><span style="color: #E1E4E8"> </span><span style="color: #6A737D">/* 没有经过 path 新的节点的数量 */</span></span>
<span class="line"><span style="color: #F97583">let</span><span style="color: #E1E4E8"> moved </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">false</span><span style="color: #E1E4E8"> </span><span style="color: #6A737D">/* 证明是否 */</span></span>
<span class="line"><span style="color: #F97583">let</span><span style="color: #E1E4E8"> maxNewIndexSoFar </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8"> </span></span>
<span class="line"><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">newIndexToOldIndexMap</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">new</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Array</span><span style="color: #E1E4E8">(toBePatched)</span></span>
<span class="line"><span style="color: #E1E4E8"> </span><span style="color: #F97583">for</span><span style="color: #E1E4E8"> (i </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8">; i </span><span style="color: #F97583">&lt;</span><span style="color: #E1E4E8"> toBePatched; i</span><span style="color: #F97583">++</span><span style="color: #E1E4E8">) newIndexToOldIndexMap[i] </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span></span>
<span class="line"><span style="color: #6A737D">/* 建立一个数组，每个子元素都是0 [ 0, 0, 0, 0, 0, 0, ] */</span></span></code></pre></div></div></figure>

<p>遍历所有新节点把索引和对应的key,存入map keyToNewIndexMap中</p>
<p><strong>keyToNewIndexMap</strong> 存放 key -&gt; index 的map</p>
<p><strong>D : 2</strong><br><strong>E : 3</strong><br><strong>C : 4</strong><br><strong>I : 5</strong></p>
<p>接下来声明一个新的指针 <strong>j</strong>,记录剩下新的节点的索引。<br><strong>patched</strong> ,记录在第⑤步patched新节点过的数量<br><strong>toBePatched</strong> 记录⑤步之前，没有经过patched 新的节点的数量。<br><strong>moved</strong>代表是否发生过移动，咱们的demo是已经发生过移动的。</p>
<p><strong>newIndexToOldIndexMap</strong> 用来存放新节点索引和老节点索引的数组。<br>newIndexToOldIndexMap 数组的index是新vnode的索引 ， value是老vnode的索引。</p>
<p>接下来</p>
<figure class="shiki javascript"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #F97583">for</span><span style="color: #E1E4E8"> (i </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> s1; i </span><span style="color: #F97583">&lt;=</span><span style="color: #E1E4E8"> e1; i</span><span style="color: #F97583">++</span><span style="color: #E1E4E8">) { </span><span style="color: #6A737D">/* 开始遍历老节点 */</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">prevChild</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> c1[i]</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (patched </span><span style="color: #F97583">&gt;=</span><span style="color: #E1E4E8"> toBePatched) { </span><span style="color: #6A737D">/* 已经patch数量大于等于， */</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #6A737D">/* ① 如果 toBePatched新的节点数量为0 ，那么统一卸载老的节点 */</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #B392F0">unmount</span><span style="color: #E1E4E8">(prevChild, parentComponent, parentSuspense, </span><span style="color: #79B8FF">true</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #F97583">continue</span></span>
<span class="line"><span style="color: #E1E4E8">       }</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">let</span><span style="color: #E1E4E8"> newIndex</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #6A737D">/* ② 如果,老节点的key存在 ，通过key找到对应的index */</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (prevChild.key </span><span style="color: #F97583">!=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">null</span><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">         newIndex </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> keyToNewIndexMap.</span><span style="color: #B392F0">get</span><span style="color: #E1E4E8">(prevChild.key)</span></span>
<span class="line"><span style="color: #E1E4E8">       } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> { </span><span style="color: #6A737D">/*  ③ 如果,老节点的key不存在 */</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #F97583">for</span><span style="color: #E1E4E8"> (j </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> s2; j </span><span style="color: #F97583">&lt;=</span><span style="color: #E1E4E8"> e2; j</span><span style="color: #F97583">++</span><span style="color: #E1E4E8">) { </span><span style="color: #6A737D">/* 遍历剩下的所有新节点 */</span></span>
<span class="line"><span style="color: #E1E4E8">           </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (</span></span>
<span class="line"><span style="color: #E1E4E8">             newIndexToOldIndexMap[j </span><span style="color: #F97583">-</span><span style="color: #E1E4E8"> s2] </span><span style="color: #F97583">===</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">&amp;&amp;</span><span style="color: #E1E4E8"> </span><span style="color: #6A737D">/* newIndexToOldIndexMap[j - s2] === 0 新节点没有被patch */</span></span>
<span class="line"><span style="color: #E1E4E8">             </span><span style="color: #B392F0">isSameVNodeType</span><span style="color: #E1E4E8">(prevChild, c2[j] </span><span style="color: #F97583">as</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">VNode</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">           ) { </span><span style="color: #6A737D">/* 如果找到与当前老节点对应的新节点那么 ，将新节点的索引，赋值给newIndex  */</span></span>
<span class="line"><span style="color: #E1E4E8">             newIndex </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> j</span></span>
<span class="line"><span style="color: #E1E4E8">             </span><span style="color: #F97583">break</span></span>
<span class="line"><span style="color: #E1E4E8">           }</span></span>
<span class="line"><span style="color: #E1E4E8">         }</span></span>
<span class="line"><span style="color: #E1E4E8">       }</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (newIndex </span><span style="color: #F97583">===</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">undefined</span><span style="color: #E1E4E8">) { </span><span style="color: #6A737D">/* ①没有找到与老节点对应的新节点，删除当前节点，卸载所有的节点 */</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #B392F0">unmount</span><span style="color: #E1E4E8">(prevChild, parentComponent, parentSuspense, </span><span style="color: #79B8FF">true</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">       } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #6A737D">/* ②把老节点的索引，记录在存放新节点的数组中， */</span></span>
<span class="line"><span style="color: #E1E4E8">         newIndexToOldIndexMap[newIndex </span><span style="color: #F97583">-</span><span style="color: #E1E4E8"> s2] </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> i </span><span style="color: #F97583">+</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (newIndex </span><span style="color: #F97583">&gt;=</span><span style="color: #E1E4E8"> maxNewIndexSoFar) {</span></span>
<span class="line"><span style="color: #E1E4E8">           maxNewIndexSoFar </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> newIndex</span></span>
<span class="line"><span style="color: #E1E4E8">         } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">           </span><span style="color: #6A737D">/* 证明有节点已经移动了   */</span></span>
<span class="line"><span style="color: #E1E4E8">           moved </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">true</span></span>
<span class="line"><span style="color: #E1E4E8">         }</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #6A737D">/* 找到新的节点进行patch节点 */</span></span>
<span class="line"><span style="color: #E1E4E8">         </span><span style="color: #B392F0">patch</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">           prevChild,</span></span>
<span class="line"><span style="color: #E1E4E8">           c2[newIndex] </span><span style="color: #F97583">as</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">VNode</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">           container,</span></span>
<span class="line"><span style="color: #E1E4E8">           </span><span style="color: #79B8FF">null</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">           parentComponent,</span></span>
<span class="line"><span style="color: #E1E4E8">           parentSuspense,</span></span>
<span class="line"><span style="color: #E1E4E8">           isSVG,</span></span>
<span class="line"><span style="color: #E1E4E8">           optimized</span></span>
<span class="line"><span style="color: #E1E4E8">         )</span></span>
<span class="line"><span style="color: #E1E4E8">         patched</span><span style="color: #F97583">++</span></span>
<span class="line"><span style="color: #E1E4E8">       }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div></div></figure>

<p>这段代码算是diff算法的核心。</p>
<p><strong>第一步： 通过老节点的key找到对应新节点的index:开始遍历老的节点，判断有没有key， 如果存在key通过新节点的keyToNewIndexMap找到与新节点index,如果不存在key那么会遍历剩下来的新节点试图找到对应index。</strong></p>
<p><strong>第二步：如果存在index证明有对应的老节点，那么直接复用老节点进行patch，没有找到与老节点对应的新节点，删除当前老节点。</strong></p>
<p><strong>第三步：newIndexToOldIndexMap找到对应新老节点关系。</strong></p>
<p>到这里，我们patch了一遍，把所有的老vnode都patch了一遍。</p>
<p>如图所示<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140428.jpeg" alt="01B449A5-9B15-4547-8EBF-5A3BA66A1831.jpg" data-caption="01B449A5-9B15-4547-8EBF-5A3BA66A1831.jpg" loading="lazy"><br>但是接下来的问题。</p>
<p><strong>1 虽然已经patch过所有的老节点。可以对于已经发生移动的节点，要怎么真正移动dom元素。</strong><br><strong>2 对于新增的节点，（图中节点I）并没有处理，应该怎么处理。</strong></p>
<figure class="shiki js"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #E1E4E8"> </span><span style="color: #6A737D">/*移动老节点创建新节点*/</span></span>
<span class="line"><span style="color: #6A737D">/* 根据最长稳定序列移动相对应的节点 */</span></span>
<span class="line"><span style="color: #E1E4E8"> </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">increasingNewIndexSequence</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> moved</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">?</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">getSequence</span><span style="color: #E1E4E8">(newIndexToOldIndexMap)</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">:</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">EMPTY_ARR</span></span>
<span class="line"><span style="color: #E1E4E8"> j </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> increasingNewIndexSequence.</span><span style="color: #79B8FF">length</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">-</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span></span>
<span class="line"><span style="color: #E1E4E8"> </span><span style="color: #F97583">for</span><span style="color: #E1E4E8"> (i </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> toBePatched </span><span style="color: #F97583">-</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span><span style="color: #E1E4E8">; i </span><span style="color: #F97583">&gt;=</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8">; i</span><span style="color: #F97583">--</span><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">nextIndex</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> s2 </span><span style="color: #F97583">+</span><span style="color: #E1E4E8"> i</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">nextChild</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> c2[nextIndex] </span><span style="color: #F97583">as</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">VNode</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">const</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">anchor</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">     nextIndex </span><span style="color: #F97583">+</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">&lt;</span><span style="color: #E1E4E8"> l2 </span><span style="color: #F97583">?</span><span style="color: #E1E4E8"> (c2[nextIndex </span><span style="color: #F97583">+</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">1</span><span style="color: #E1E4E8">] </span><span style="color: #F97583">as</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">VNode</span><span style="color: #E1E4E8">).el </span><span style="color: #F97583">:</span><span style="color: #E1E4E8"> parentAnchor</span></span>
<span class="line"><span style="color: #E1E4E8">   </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (newIndexToOldIndexMap[i] </span><span style="color: #F97583">===</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8">) { </span><span style="color: #6A737D">/* 没有老的节点与新的节点对应，则创建一个新的vnode */</span></span>
<span class="line"><span style="color: #E1E4E8">     </span><span style="color: #B392F0">patch</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #79B8FF">null</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">       nextChild,</span></span>
<span class="line"><span style="color: #E1E4E8">       container,</span></span>
<span class="line"><span style="color: #E1E4E8">       anchor,</span></span>
<span class="line"><span style="color: #E1E4E8">       parentComponent,</span></span>
<span class="line"><span style="color: #E1E4E8">       parentSuspense,</span></span>
<span class="line"><span style="color: #E1E4E8">       isSVG</span></span>
<span class="line"><span style="color: #E1E4E8">     )</span></span>
<span class="line"><span style="color: #E1E4E8">   } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (moved) {</span></span>
<span class="line"><span style="color: #E1E4E8">     </span><span style="color: #F97583">if</span><span style="color: #E1E4E8"> (j </span><span style="color: #F97583">&lt;</span><span style="color: #E1E4E8"> </span><span style="color: #79B8FF">0</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">||</span><span style="color: #E1E4E8"> i </span><span style="color: #F97583">!==</span><span style="color: #E1E4E8"> increasingNewIndexSequence[j]) { </span><span style="color: #6A737D">/*如果没有在长*/</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #6A737D">/* 需要移动的vnode */</span></span>
<span class="line"><span style="color: #E1E4E8">       </span><span style="color: #B392F0">move</span><span style="color: #E1E4E8">(nextChild, container, anchor, MoveType.</span><span style="color: #79B8FF">REORDER</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">     } </span><span style="color: #F97583">else</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">       j</span><span style="color: #F97583">--</span></span>
<span class="line"><span style="color: #E1E4E8">     }</span></span></code></pre></div></div></figure>

<h3 id="⑥最长稳定序列"><a href="#⑥最长稳定序列" class="headerlink" title="⑥最长稳定序列"></a>⑥最长稳定序列</h3><p>首选通过getSequence得到一个最长稳定序列，对于index &#x3D;&#x3D;&#x3D; 0 的情况也就是<strong>新增节点（图中I）</strong> 需要从新mount一个新的vnode,然后对于发生移动的节点进行统一的移动操作</p>
<p><strong>什么叫做最长稳定序列</strong></p>
<p>对于以下的原始序列<br>0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15<br>最长递增子序列为<br>0, 2, 6, 9, 11, 15.</p>
<p><strong>为什么要得到最长稳定序列</strong></p>
<p>因为我们需要一个序列作为基础的参照序列，其他未在稳定序列的节点，进行移动。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>经过上述我们大致知道了diff算法的流程<br><strong>1 从头对比找到有相同的节点 patch ，发现不同，立即跳出。</strong></p>
<p><strong>2如果第一步没有patch完，立即，从后往前开始patch ,如果发现不同立即跳出循环。</strong></p>
<p><strong>3如果新的节点大于老的节点数 ，对于剩下的节点全部以新的vnode处理（ 这种情况说明已经patch完相同的vnode ）。</strong></p>
<p><strong>4 对于老的节点大于新的节点的情况 ， 对于超出的节点全部卸载 （ 这种情况说明已经patch完相同的vnode ）。</strong></p>
<p><strong>5不确定的元素（ 这种情况说明没有patch完相同的vnode ） 与 3 ，4对立关系。</strong></p>
<p>1 把没有比较过的新的vnode节点,通过map保存<br>记录已经patch的新节点的数量 patched<br>没有经过 path 新的节点的数量 toBePatched<br>建立一个数组newIndexToOldIndexMap，每个子元素都是[ 0, 0, 0, 0, 0, 0, ] 里面的数字记录老节点的索引 ，数组索引就是新节点的索引<br><strong>开始遍历老节点</strong><br>① 如果 toBePatched新的节点数量为0 ，那么统一卸载老的节点<br>② 如果,老节点的key存在 ，通过key找到对应的index<br>③ 如果,老节点的key不存在</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">1 遍历剩下的所有新节点</span></span>
<span class="line"><span style="color: #e1e4e8">2 如果找到与当前老节点对应的新节点那么 ，将新节点的索引，赋值给newIndex</span></span></code></pre></div></div></figure>

<p>④ 没有找到与老节点对应的新节点，卸载当前老节点。<br>⑤ 如果找到与老节点对应的新节点，把老节点的索引，记录在存放新节点的数组中，</p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">1 如果节点发生移动 记录已经移动了</span></span>
<span class="line"><span style="color: #e1e4e8">2 patch新老节点 找到新的节点进行patch节点</span></span></code></pre></div></div></figure>

<p><strong>遍历结束</strong></p>
<p><strong>如果发生移动</strong></p>
<figure class="shiki"><div class='codeblock'><div class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></div><div class="code"><pre class="shiki github-dark"><code><span class="line"><span style="color: #e1e4e8">① 根据 newIndexToOldIndexMap 新老节点索引列表找到最长稳定序列</span></span>
<span class="line"><span style="color: #e1e4e8">② 对于 newIndexToOldIndexMap -item =0 证明不存在老节点 ，从新形成新的vnode </span></span>
<span class="line"><span style="color: #e1e4e8">③ 对于发生移动的节点进行移动处理。</span></span></code></pre></div></div></figure>

<h2 id="三-key的作用，如何正确key。"><a href="#三-key的作用，如何正确key。" class="headerlink" title="三 key的作用，如何正确key。"></a>三 key的作用，如何正确key。</h2><h3 id="1key的作用"><a href="#1key的作用" class="headerlink" title="1key的作用"></a>1key的作用</h3><p>在我们上述diff算法中，通过isSameVNodeType方法判断，来判断key是否相等判断新老节点。<br>那么由此我们可以总结出？</p>
<p><strong>在v-for循环中，key的作用是：通过判断newVnode和OldVnode的key是否相等，从而复用与新节点对应的老节点，节约性能的开销。</strong></p>
<h3 id="2如何正确使用key"><a href="#2如何正确使用key" class="headerlink" title="2如何正确使用key"></a>2如何正确使用key</h3><h4 id="①错误用法-1：用index做key。"><a href="#①错误用法-1：用index做key。" class="headerlink" title="①错误用法 1：用index做key。"></a>①错误用法 1：用index做key。</h4><p><strong>用index做key的效果实际和没有用diff算法是一样的，为什么这么说呢，下面我就用一幅图来说明：</strong></p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140436.jpeg" alt="WechatIMG10408 1.jpeg" data-caption="WechatIMG10408 1.jpeg" loading="lazy"></p>
<p>如果所示当我们用index作为key的时候，无论我们怎么样移动删除节点，到了diff算法中都会从头到尾依次patch(图中：<strong>所有节点均未有效的复用</strong>)</p>
<h4 id="②错误用法2-：用index拼接其他值作为key。"><a href="#②错误用法2-：用index拼接其他值作为key。" class="headerlink" title="②错误用法2 ：用index拼接其他值作为key。"></a>②错误用法2 ：用index拼接其他值作为key。</h4><p>当已用index拼接其他值作为索引的时候，因为每一个节点都找不到对应的key，导致所有的节点都不能复用,所有的新vnode都需要重新创建。都需要重新create</p>
<p>如图所示。<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140441.jpeg" alt="WechatIMG10531.jpeg" data-caption="WechatIMG10531.jpeg" loading="lazy"></p>
<h4 id="③正确用法-：用唯一值id做key-我们可以用前后端交互的数据源的id为key-。"><a href="#③正确用法-：用唯一值id做key-我们可以用前后端交互的数据源的id为key-。" class="headerlink" title="③正确用法 ：用唯一值id做key(我们可以用前后端交互的数据源的id为key)。"></a>③正确用法 ：用唯一值id做key(我们可以用前后端交互的数据源的id为key)。</h4><p>如图所示。每一个节点都做到了复用。起到了diff算法的真正作用。</p>
<p><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140449.jpeg" alt="WechatIMG10532.jpeg" data-caption="WechatIMG10532.jpeg" loading="lazy"></p>
<h2 id="四-总结"><a href="#四-总结" class="headerlink" title="四 总结"></a>四 总结</h2><p>我们在上面，已经把刚开始的问题统统解决了，最后用一张思维脑图来从新整理一下整个流程。<br><img onerror="imgOnError(this);" data-fancybox="gallery" src="https://cdn.tangyuxian.com/PicGoImages/20210812140457.jpeg" alt="7FB635D9-EE62-45D5-8DE9-620CDF74B772.jpg" data-caption="7FB635D9-EE62-45D5-8DE9-620CDF74B772.jpg" loading="lazy"></p>
<hr>
<p>参考文章:</p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/weixin_39621975/article/details/111213826">CSDN文章:diff算法_Vue3.0时代你必须了解的：diff算法原理和优化</a></p>
<p><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000023060173">vue3.0 diff算法详解(超详细)</a></p>

    
  </article>

  
      
    <div class="nexmoe-post-copyright">
        <strong>本文作者：</strong>tangyuxian<br>
        
        <strong>本文链接：</strong><a href="https://www.tangyuxian.com/2021/07/14/%E5%89%8D%E7%AB%AF/vue/vue-diff%E7%AE%97%E6%B3%95/" title="https:&#x2F;&#x2F;www.tangyuxian.com&#x2F;2021&#x2F;07&#x2F;14&#x2F;%E5%89%8D%E7%AB%AF&#x2F;vue&#x2F;vue-diff%E7%AE%97%E6%B3%95&#x2F;" target="_blank" rel="noopener">https:&#x2F;&#x2F;www.tangyuxian.com&#x2F;2021&#x2F;07&#x2F;14&#x2F;%E5%89%8D%E7%AB%AF&#x2F;vue&#x2F;vue-diff%E7%AE%97%E6%B3%95&#x2F;</a><br>

        
            <strong>版权声明：</strong>本文采用 <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/cn/deed.zh" target="_blank">CC BY-NC-SA 3.0 CN</a> 协议进行许可

        
    </div>


  
  
  <div class="nexmoe-post-meta nexmoe-rainbow">
   
    
        <a class="nexmoefont icon-tag-fill -none-link" href="/tags/vue/" rel="tag">vue</a>
    
</div>
  
  
    <script async src="/js/copy-codeblock.js?v=1763992123960"></script>
  

  
      <div class="nexmoe-post-footer">
          <div class="valine"></div>
<script src='https://lib.baomitu.com/valine/1.3.9/Valine.min.js'></script>
<script>
  new Valine({
      el: '.valine',
      appId: 'r5zxC0st0DDjPA9auXzMV7HY-gzGzoHsz',
      appKey: '3bqCsovpyfTPHUzTHovd3V3V'
  })
</script>

      </div>
  
</div></div><div class="nexmoe-post-right">    <div class="nexmoe-fixed">
        <div class="nexmoe-tool">

            

            
            
            <button class="mdui-fab catalog" style="overflow:unset;">
                <i class="nexmoefont icon-i-catalog"></i>
                <div class="nexmoe-toc">
                    <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BC%95%E4%B8%80-virtual-dom"><span class="toc-number">1.</span> <span class="toc-text">引一 virtual dom</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFvirtual-dom"><span class="toc-number">1.1.</span> <span class="toc-text">什么是virtual dom</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BC%95%E4%BA%8C-%E5%88%86%E6%9E%90diff%E7%AE%97%E6%B3%95"><span class="toc-number">2.</span> <span class="toc-text">引二 分析diff算法</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Vue2-x-diff%E7%AE%97%E6%B3%95"><span class="toc-number">2.1.</span> <span class="toc-text">Vue2.x diff算法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Vue2-x-diff%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86"><span class="toc-number">2.2.</span> <span class="toc-text">Vue2.x diff算法原理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Vue3-0-diff"><span class="toc-number">2.3.</span> <span class="toc-text">Vue3.0 diff</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#diff%E7%97%9B%E7%82%B9"><span class="toc-number">2.3.1.</span> <span class="toc-text">diff痛点</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%8A%A8%E9%9D%99%E7%BB%93%E5%90%88-PatchFlag"><span class="toc-number">2.3.1.1.</span> <span class="toc-text">动静结合 PatchFlag</span></a></li></ol></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%B8%80-%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E7%94%A8%E5%88%B0%E4%BA%86diff%E7%AE%97%E6%B3%95-diff%E7%AE%97%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F%EF%BC%9F"><span class="toc-number">3.</span> <span class="toc-text">一 什么时候用到了diff算法,diff算法作用域？</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-1diff%E7%AE%97%E6%B3%95%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F"><span class="toc-number">3.1.</span> <span class="toc-text">1.1diff算法的作用域</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-2-patchChildren"><span class="toc-number">3.2.</span> <span class="toc-text">1.2 patchChildren</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-3-diff%E7%AE%97%E6%B3%95%E4%BD%9C%E7%94%A8%EF%BC%9F"><span class="toc-number">3.3.</span> <span class="toc-text">1.3 diff算法作用？</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BA%8C-diff%E7%AE%97%E6%B3%95%E5%85%B7%E4%BD%93%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88-%E9%87%8D%E7%82%B9-%EF%BC%9F"><span class="toc-number">4.</span> <span class="toc-text">二 diff算法具体做了什么(重点)？</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A0%E7%AC%AC%E4%B8%80%E6%AD%A5%E4%BB%8E%E5%A4%B4%E5%BC%80%E5%A7%8B%E5%90%91%E5%B0%BE%E5%AF%BB%E6%89%BE"><span class="toc-number">4.1.</span> <span class="toc-text">①第一步从头开始向尾寻找</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A1%E7%AC%AC%E4%BA%8C%E6%AD%A5%E4%BB%8E%E5%B0%BE%E5%BC%80%E5%A7%8B%E5%90%8C%E5%89%8Ddiff"><span class="toc-number">4.2.</span> <span class="toc-text">②第二步从尾开始同前diff</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A2-%E5%A6%82%E6%9E%9C%E8%80%81%E8%8A%82%E7%82%B9%E6%98%AF%E5%90%A6%E5%85%A8%E9%83%A8patch%EF%BC%8C%E6%96%B0%E8%8A%82%E7%82%B9%E6%B2%A1%E6%9C%89%E8%A2%ABpatch%E5%AE%8C-%E5%88%9B%E5%BB%BA%E6%96%B0%E7%9A%84vnode"><span class="toc-number">4.3.</span> <span class="toc-text">③ 如果老节点是否全部patch，新节点没有被patch完,创建新的vnode</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A3-%E5%A6%82%E6%9E%9C%E6%96%B0%E8%8A%82%E7%82%B9%E5%85%A8%E9%83%A8%E8%A2%ABpatch%EF%BC%8C%E8%80%81%E8%8A%82%E7%82%B9%E6%9C%89%E5%89%A9%E4%BD%99%EF%BC%8C%E9%82%A3%E4%B9%88%E5%8D%B8%E8%BD%BD%E6%89%80%E6%9C%89%E8%80%81%E8%8A%82%E7%82%B9"><span class="toc-number">4.4.</span> <span class="toc-text">④ 如果新节点全部被patch，老节点有剩余，那么卸载所有老节点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A4-%E4%B8%8D%E7%A1%AE%E5%AE%9A%E7%9A%84%E5%85%83%E7%B4%A0-%EF%BC%88-%E8%BF%99%E7%A7%8D%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%E6%B2%A1%E6%9C%89patch%E5%AE%8C%E7%9B%B8%E5%90%8C%E7%9A%84vnode-%EF%BC%89%EF%BC%8C%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E6%8E%A5%E7%9D%80%E2%91%A0%E2%91%A1%E7%9A%84%E9%80%BB%E8%BE%91%E7%BB%A7%E7%BB%AD%E5%BE%80%E4%B8%8B%E7%9C%8B"><span class="toc-number">4.5.</span> <span class="toc-text">⑤ 不确定的元素 （ 这种情况说明没有patch完相同的vnode ），我们可以接着①②的逻辑继续往下看</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E2%91%A5%E6%9C%80%E9%95%BF%E7%A8%B3%E5%AE%9A%E5%BA%8F%E5%88%97"><span class="toc-number">4.6.</span> <span class="toc-text">⑥最长稳定序列</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">4.7.</span> <span class="toc-text">总结</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%B8%89-key%E7%9A%84%E4%BD%9C%E7%94%A8%EF%BC%8C%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AEkey%E3%80%82"><span class="toc-number">5.</span> <span class="toc-text">三 key的作用，如何正确key。</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1key%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-number">5.1.</span> <span class="toc-text">1key的作用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8key"><span class="toc-number">5.2.</span> <span class="toc-text">2如何正确使用key</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E2%91%A0%E9%94%99%E8%AF%AF%E7%94%A8%E6%B3%95-1%EF%BC%9A%E7%94%A8index%E5%81%9Akey%E3%80%82"><span class="toc-number">5.2.1.</span> <span class="toc-text">①错误用法 1：用index做key。</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E2%91%A1%E9%94%99%E8%AF%AF%E7%94%A8%E6%B3%952-%EF%BC%9A%E7%94%A8index%E6%8B%BC%E6%8E%A5%E5%85%B6%E4%BB%96%E5%80%BC%E4%BD%9C%E4%B8%BAkey%E3%80%82"><span class="toc-number">5.2.2.</span> <span class="toc-text">②错误用法2 ：用index拼接其他值作为key。</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E2%91%A2%E6%AD%A3%E7%A1%AE%E7%94%A8%E6%B3%95-%EF%BC%9A%E7%94%A8%E5%94%AF%E4%B8%80%E5%80%BCid%E5%81%9Akey-%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E7%94%A8%E5%89%8D%E5%90%8E%E7%AB%AF%E4%BA%A4%E4%BA%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90%E7%9A%84id%E4%B8%BAkey-%E3%80%82"><span class="toc-number">5.2.3.</span> <span class="toc-text">③正确用法 ：用唯一值id做key(我们可以用前后端交互的数据源的id为key)。</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9B%9B-%E6%80%BB%E7%BB%93"><span class="toc-number">6.</span> <span class="toc-text">四 总结</span></a></li></ol>
                </div>
            </button>
            

            

            <a href="#nexmoe-content" class="backtop toc-link" aria-label="Back To Top" title="top"><button class="mdui-fab mdui-ripple"><i class="nexmoefont icon-caret-top"></i></button></a>
        </div>
    </div>
</div></div><div id="nexmoe-pendant"><div class="nexmoe-drawer mdui-drawer nexmoe-pd" id="drawer">
    
        
            <div class="nexmoe-pd-item">
                <div class="clock">
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="memory"></div>
        <div class="needle" id="hours"></div>
        <div class="needle" id="minutes"></div>
        <div class="needle" id="seconds"></div>
        <div class="clock_logo">

        </div>

    </div>
<script>
        // ----- Variable declaration

        var data = {
                dt: new Date(),
                hours: {
                        el: document.querySelector('#hours'),
                        val: 0 },

                minutes: {
                        el: document.querySelector('#minutes'),
                        val: 0 },

                seconds: {
                        el: document.querySelector('#seconds'),
                        val: 0 },

                renderTime: 1000 };


        // ----- Clock rendering logic

        updateTime = () => {
                let hour = data.dt.getHours();
                let minute = data.dt.getMinutes();
                let second = data.dt.getSeconds();

                data.hours.el.classList.add('moving');
                data.minutes.el.classList.add('moving');
                data.seconds.el.classList.add('moving');

                data.hours.val = hour == 0 && data.hours.val == 59 * 6 * 5 ? 360 : hour * 6 * 5;
                data.minutes.val = minute == 0 && data.minutes.val == 59 * 6 ? 360 : minute * 6;
                data.seconds.val = second == 0 && data.seconds.val == 59 * 6 ? 360 : second * 6;

                data.hours.el.style.transform = `translate(-50%, -75%) rotate(${data.hours.val}deg)`;
                data.minutes.el.style.transform = `translate(-50%, -75%) rotate(${data.minutes.val}deg)`;
                data.seconds.el.style.transform = `translate(-50%, -75%) rotate(${data.seconds.val}deg)`;

                data.dt.setSeconds(second + 1);
        };

        // ----- Clock execution

        setInterval(() => {
                updateTime();
        }, data.renderTime);

        updateTime();

        // ----- Events area

        data.seconds.el.addEventListener('transitionend', () => {
                data.hours.el.classList.remove('moving');
                data.minutes.el.classList.remove('moving');
                data.seconds.el.classList.remove('moving');

                // if (data.hours.val == 360) {
                //   data.hours.el.style.transform = 'translate(-50%, -75%) rotate(0deg)';
                // }

                if (data.minutes.val == 360) {
                        data.minutes.el.style.transform = 'translate(-50%, -75%) rotate(0deg)';
                }

                if (data.seconds.val == 360) {
                        data.seconds.el.style.transform = 'translate(-50%, -75%) rotate(0deg)';
                }
        });

</script>
<style>
    .clock {
        background-color: #ffffff;
        width: 70vw;
        height: 70vw;
        max-width: 70vh;
        max-height: 70vh;
        border: solid 2.8vw var(--color-primary);
        position: relative;
        overflow: hidden;
        border-radius: 50%;
        box-sizing: border-box;
        box-shadow: 0 1.4vw 2.8vw var(--color2);
        zoom:0.2
    }

    .memory {
        position: absolute;
        top: 50%;
        left: 50%;
        transform-origin: center;
    }

    .memory:nth-child(1) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(0deg) translateY(-520%);
    }

    .memory:nth-child(2) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(6deg) translateY(-1461%);
    }

    .memory:nth-child(3) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(12deg) translateY(-1461%);
    }

    .memory:nth-child(4) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(18deg) translateY(-1461%);
    }

    .memory:nth-child(5) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(24deg) translateY(-1461%);
    }

    .memory:nth-child(6) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(30deg) translateY(-520%);
    }

    .memory:nth-child(7) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(36deg) translateY(-1461%);
    }

    .memory:nth-child(8) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(42deg) translateY(-1461%);
    }

    .memory:nth-child(9) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(48deg) translateY(-1461%);
    }

    .memory:nth-child(10) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(54deg) translateY(-1461%);
    }

    .memory:nth-child(11) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(60deg) translateY(-520%);
    }

    .memory:nth-child(12) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(66deg) translateY(-1461%);
    }

    .memory:nth-child(13) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(72deg) translateY(-1461%);
    }

    .memory:nth-child(14) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(78deg) translateY(-1461%);
    }

    .memory:nth-child(15) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(84deg) translateY(-1461%);
    }

    .memory:nth-child(16) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(90deg) translateY(-520%);
    }

    .memory:nth-child(17) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(96deg) translateY(-1461%);
    }

    .memory:nth-child(18) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(102deg) translateY(-1461%);
    }

    .memory:nth-child(19) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(108deg) translateY(-1461%);
    }

    .memory:nth-child(20) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(114deg) translateY(-1461%);
    }

    .memory:nth-child(21) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(120deg) translateY(-520%);
    }

    .memory:nth-child(22) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(126deg) translateY(-1461%);
    }

    .memory:nth-child(23) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(132deg) translateY(-1461%);
    }

    .memory:nth-child(24) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(138deg) translateY(-1461%);
    }

    .memory:nth-child(25) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(144deg) translateY(-1461%);
    }

    .memory:nth-child(26) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(150deg) translateY(-520%);
    }

    .memory:nth-child(27) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(156deg) translateY(-1461%);
    }

    .memory:nth-child(28) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(162deg) translateY(-1461%);
    }

    .memory:nth-child(29) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(168deg) translateY(-1461%);
    }

    .memory:nth-child(30) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(174deg) translateY(-1461%);
    }

    .memory:nth-child(31) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(180deg) translateY(-520%);
    }

    .memory:nth-child(32) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(186deg) translateY(-1461%);
    }

    .memory:nth-child(33) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(192deg) translateY(-1461%);
    }

    .memory:nth-child(34) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(198deg) translateY(-1461%);
    }

    .memory:nth-child(35) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(204deg) translateY(-1461%);
    }

    .memory:nth-child(36) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(210deg) translateY(-520%);
    }

    .memory:nth-child(37) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(216deg) translateY(-1461%);
    }

    .memory:nth-child(38) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(222deg) translateY(-1461%);
    }

    .memory:nth-child(39) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(228deg) translateY(-1461%);
    }

    .memory:nth-child(40) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(234deg) translateY(-1461%);
    }

    .memory:nth-child(41) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(240deg) translateY(-520%);
    }

    .memory:nth-child(42) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(246deg) translateY(-1461%);
    }

    .memory:nth-child(43) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(252deg) translateY(-1461%);
    }

    .memory:nth-child(44) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(258deg) translateY(-1461%);
    }

    .memory:nth-child(45) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(264deg) translateY(-1461%);
    }

    .memory:nth-child(46) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(270deg) translateY(-520%);
    }

    .memory:nth-child(47) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(276deg) translateY(-1461%);
    }

    .memory:nth-child(48) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(282deg) translateY(-1461%);
    }

    .memory:nth-child(49) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(288deg) translateY(-1461%);
    }

    .memory:nth-child(50) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(294deg) translateY(-1461%);
    }

    .memory:nth-child(51) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(300deg) translateY(-520%);
    }

    .memory:nth-child(52) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(306deg) translateY(-1461%);
    }

    .memory:nth-child(53) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(312deg) translateY(-1461%);
    }

    .memory:nth-child(54) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(318deg) translateY(-1461%);
    }

    .memory:nth-child(55) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(324deg) translateY(-1461%);
    }

    .memory:nth-child(56) {
        background-color: var(--color-primary);
        width: 2%;
        height: 8%;
        transform: translate(-50%, -50%) rotate(330deg) translateY(-520%);
    }

    .memory:nth-child(57) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(336deg) translateY(-1461%);
    }

    .memory:nth-child(58) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(342deg) translateY(-1461%);
    }

    .memory:nth-child(59) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(348deg) translateY(-1461%);
    }

    .memory:nth-child(60) {
        background-color: #949494;
        width: 1%;
        height: 3%;
        transform: translate(-50%, -50%) rotate(354deg) translateY(-1461%);
    }

    .needle {
        position: absolute;
        top: 50%;
        left: 50%;
        transform-origin: center;
    }

    .needle#hours {
        background-color: #1f1f1f;
        width: 4%;
        height: 30%;
        transform-origin: center 75%;
        transform: translate(-50%, -75%);
    }

    .needle#hours.moving {
        transition: transform 150ms ease-out;
    }

    .needle#hours:after {
        content: '';
        background-color: #1f1f1f;
        width: 4vw;
        height: 4vw;
        max-width: 4vh;
        max-height: 4vh;
        display: block;
        position: absolute;
        top: 75%;
        left: 50%;
        box-sizing: border-box;
        border-radius: 50%;
        transform: translate(-50%, -50%);
    }

    .needle#minutes {
        background-color: #1f1f1f;
        width: 2%;
        height: 45%;
        transform-origin: center 75%;
        transform: translate(-50%, -75%);
    }

    .needle#minutes.moving {
        transition: transform 150ms ease-out;
    }

    .needle#minutes:after {
        content: '';
        background-color: #1f1f1f;
        width: 4vw;
        height: 4vw;
        max-width: 4vh;
        max-height: 4vh;
        display: block;
        position: absolute;
        top: 75%;
        left: 50%;
        box-sizing: border-box;
        border-radius: 50%;
        transform: translate(-50%, -50%);
    }

    .needle#seconds {
        background-color: #cb2f2f;
        width: 1%;
        height: 50%;
        transform-origin: center 75%;
        transform: translate(-50%, -75%);
    }

    .needle#seconds.moving {
        transition: transform 150ms ease-out;
    }

    .needle#seconds:after {
        content: '';
        background-color: #cb2f2f;
        width: 2.5vw;
        height: 2.5vw;
        max-width: 2.5vh;
        max-height: 2.5vh;
        display: block;
        position: absolute;
        top: 75%;
        left: 50%;
        box-sizing: border-box;
        border-radius: 50%;
        transform: translate(-50%, -50%);
    }
    .clock_logo{
        width: 10vw;
        height: 10vw;
        max-width: 10vh;
        max-height: 10vh;
        position: absolute;
        top: 50%;
        left: 50%;
        box-sizing: border-box;
        border-radius: 50%;
        transform: translate(-50%, -50%);
        background-size: 100% 100%;
        background-repeat: no-repeat;
    }
    .clock_logo{
            width: 10vw;
            height: 10vw;
            max-width: 10vh;
            max-height: 10vh;
            position: absolute;
            top: 50%;
            left: 50%;
            box-sizing: border-box;
            border-radius: 50%;
            transform: translate(-50%, -50%);
            background-image: var(--clock-logo);
            background-size: 100% 100%;
            background-repeat: no-repeat;
    }
    @media (min-width: 100vh) {
        .clock {
            border: solid 2.8vh var(--color-primary);
            box-shadow: 0 1.4vh 2.8vh var(--color2);
        }
    }

</style>





            </div>
        
            <div class="nexmoe-pd-item">
                <div class="qweather" >
    <div id="he-plugin-standard">
        <iframe allowtransparency="true" frameborder="0" width="304" height="96" scrolling="no" src="//tianqi.2345.com/plugin/widget/index.htm?s=2&z=1&t=0&v=0&d=2&bd=0&k=000000&f=ff8040&ltf=009944&htf=cc0000&q=1&e=1&a=1&c=54511&w=300&h=96&align=center"></iframe>
    </div>
    <div class="qweather-logo">

    </div>
</div>
<style>
    .qweather{
        position: relative;
        padding-right: 5px;
    }
    .qweather #he-plugin-standard {
        border: 2px solid var(--color-primary);
        box-shadow: 0 1px 0 0 var(--color-primary), 0 1px 2px 0 var(--color-primary);
        border-radius: 8px;
        width: 300px;
        overflow: hidden;
    }
    .qweather-logo{
        position: absolute;
        right: 0;
        top: -15px;
        width: 40px;
        height: 40px;
        background-image: var(--qweather-logo);
        background-size: 100% 100%;
        background-repeat: no-repeat;
    }
</style>

            </div>
        
    
</div>
<style>
    .nexmoe-pd {
        left: auto;
        top: 40px;
        right: 0;
    }
    .nexmoe-pd-item{
       display: flex;
        justify-content: center;
        margin-bottom: 30px;
    }
    #nexmoe-pendant .mdui-drawer{
        width: 305px;
    }
    @media screen and (max-width: 1600px) {
        #nexmoe-pendant {
            display: none
        }
    }
</style>
</div><div id="nexmoe-footer"><!--!--></div><div id="nexmoe-search-space"><div class="search-container"><div class="search-header"><div class="search-input-container"><input class="search-input" type="text" placeholder="搜索" onInput="sinput();"></div><a class="search-close" onclick="sclose();">×</a></div><div class="search-body"></div></div></div><div></div><!-- hexo injector body_end start -->
<script src="https://cdn.jsdelivr.net/npm/hexo-shiki-plugin@latest/lib/codeblock.js"></script>

  <script>
  const CODE_CONFIG = {
    beautify: true,
    highlightCopy: true,
    highlightLang: true,
    highlightHeightLimit: null,
    isHighlightShrink: false,
    copy: {
      success: 'Copy Success',
      error: 'Copy Error',
      noSupport: 'Browser Not Support',
    }
  };
  console.log(
    `%c hexo-shiki-plugin %c v1.0.27 %c https://github.com/nova1751/hexo-shiki-plugin`,
    "color: #fff; background: #5f5f5f",
    "color: #fff; background: #80c8f8",
    ""
  );
  </script>
  <!-- hexo injector body_end end --><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"model":{"jsonPath":"/live2dw/assets/xiaomai.model.json"},"display":{"position":"left","width":200,"height":500},"mobile":{"show":false},"react":{"opacity":0.9},"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body></html>